package org.novelhibernate.core.sqlregistry;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.jdom.Element;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
import org.novelhibernate.core.ParameterMapper;
import org.novelhibernate.util.SQLParser;

public class ConfigurationBasedSQLRegistry extends AbstractSQLRegistry {

	private SQLParser parser = SQLParser.getInstance();

	private static final Logger log = Logger
			.getLogger(ConfigurationBasedSQLRegistry.class);

	/**
	 * loading the configuration file
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public ConfigurationBasedSQLRegistry() throws Exception {

		// identify the classpath
		String classpath = ConfigurationBasedSQLRegistry.class
				.getProtectionDomain().getCodeSource().getLocation().getFile();

		SAXBuilder builder = new SAXBuilder();

		Document doc = builder.build(new File(classpath
				+ "novelhibernate.cfg.xml"));

		// identify if there is need to load external sql
		Properties props = null;
		if (doc.getRootElement().getAttribute("sql-location") != null)
			props = loadExternalSql(classpath, doc.getRootElement()
					.getAttribute("sql-location").getValue());

		ParameterMapper[] mappers = null;

		String sql;

		Class<?> clazz;

		List<Element> elements = doc.getRootElement().getChildren();
		List<Element> subElements = null;
		// loop the elements
		for (Element element : elements) {

			sql = (props==null)? element.getChildText("sql") : props.getProperty(element.getChildText("sql"));
			clazz = (element.getChildText("class") == null)? null : Class.forName(element.getChildText("class"));

			int start = 0;

			if (null != element.getChild("aliases")
					&& element.getChild("aliases").getChildren().size() != 0)
				mappers = new ParameterMapper[element.getChild("aliases")
						.getChildren().size()];

			// loading the aliases
			if (null != element.getChild("aliases")) {
				subElements = element.getChild("aliases").getChildren();

				for (Element ele : subElements) {
					
					mappers[start] = new ParameterMapper(ele.getAttribute(
							"param").getValue(), Integer.parseInt(ele
							.getAttribute("index").getValue()));
					start++;
				}
			}

			
			// identify the sql
			if (sql.trim().startsWith("insert"))
				registerCreateSql(sql, clazz);
			else if (sql.trim().startsWith("update"))
				registerUpdateSql(sql, clazz, mappers);
			else if (sql.trim().startsWith("delete"))
				registerDeleteSql(sql, clazz, mappers);

		}

		if (null != props)
			props.clear();

	}

	@Override
	public synchronized void registerCreateSql(String sql, Class<?> target) {

		log
				.debug("[Configuration based regisration]register in advance for insert SQL: "
						+ sql);
		try {

			registry.put(sql.replaceAll("\\s*", ""),
					registerInsertStatement(target));

		} catch (Throwable t) {

			t.printStackTrace();
		}
	}

	@Override
	public synchronized void registerUpdateSql(String sql, Class<?> target,
			ParameterMapper[] mappers) {

		log
				.debug("[Configuration based regisration]register in advance for update SQL: "
						+ sql);

		if (null == target)
			log.warn("[Configuration based regisration]target class is not setted for SQL: "
						+ sql);
		try {

			Object[] parameters = parser.parseUpdateSQL(sql, target);
			registry.put(sql.replaceAll("\\s*", ""),
					registerUpdateAndDeleteStatement(target, parameters,
							mappers));

		} catch (Throwable t) {

			t.printStackTrace();
		}

	}

	@Override
	public synchronized void registerDeleteSql(String sql, Class<?> target,
			ParameterMapper[] mappers) {

		log
				.debug("[Configuration based regisration]register in advance for delete SQL: "
						+ sql);
		
		if (null == target)
			log.warn("[Configuration based regisration]target class is not setted for SQL: "
						+ sql);

		try {

			List<Integer> restrict = new ArrayList<Integer>();
			if (null != mappers)
				for (ParameterMapper mapper : mappers)
					restrict.add(mapper.getIndex());

			Object[] parameters = parser.parseDeleteSQL(sql, restrict);
			registry.put(sql.replaceAll("\\s*", ""),
					registerUpdateAndDeleteStatement(target, parameters,
							mappers));

		} catch (Throwable t) {

			t.printStackTrace();
		}

	}

	/**
	 * load the external sql file, in case the actual sql is maintained in some where else
	 * @param classpath the classpath
	 * @param location the location, can be either relevant or absolute
	 * @return the property that loaded with sql file
	 * @throws IOException
	 */
	private Properties loadExternalSql(String classpath, String location)
			throws IOException {

		Properties props = new Properties();

		FileInputStream stream = (location.startsWith("classpath:")) ? new FileInputStream(
				classpath + location)
				: new FileInputStream(location);

		props.load(stream);

		stream.close();

		return props;

	}

}
